package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.feign.clients.ArticleClient;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.dtos.SaveArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.mapper.WmNewsMaterialMapper;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.service.IWmChannelService;
import com.heima.wemedia.service.IWmNewsAutoScanService;
import com.heima.wemedia.service.IWmNewsService;
import com.heima.wemedia.service.IWmUserService;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.TesseractException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 自媒体文章发布审核
 */
@Service
public class WmNewsAutoScanServiceImpl implements IWmNewsAutoScanService {
    @Autowired
    private IWmNewsService wmNewsService;
    @Autowired
    private GreenTextScan greenTextScan;
    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private IWmUserService wmUserService;
    @Autowired
    private IWmChannelService wmChannelService;
    @Autowired
    private ArticleClient articleClient;
    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;
    @Autowired
    private Tess4jClient tess4jClient;

    @Override
    @Async //当前方法是异步调用
    public void AutoScanTextAndImages(Integer id) {
        //根据id去数据库查询文章内容对象
        WmNews wmNews = wmNewsService.getById(id);
        if (wmNews == null) {
            //不能查询到则抛出一个异常----文章不存在,无法审核
            throw new RuntimeException("文章不存在,无法审核");
        }
        //判断图文数据是否为提交待审核状态
        if (wmNews.getStatus() == 1){
            //能查询到文章对象,则获取出对象中的文本内容和图片内容
            Map<String, Object> textAndImages = getTextAndImages(wmNews);
            String content = (String) textAndImages.get("content");
            List<String> imagesAddress = (List<String>) textAndImages.get("images");
            //审核自管理的敏感词,审核文本内容中是否出现自管理的敏感词,审核失败需要修改wmNews的reason属性,审核成功则不需要做任何修改
            Boolean sensitiveTest = TestSensitiveScan(content, wmNews);
            if (!sensitiveTest){
                return;
            }
            //对文本内容进行审核,审核失败需要修改wmNews的reason属性,审核成功则不需要做任何修改
            Boolean textTest = autoTestTextScan(content, wmNews);

            if (!textTest) {
                return;
            }
            //对图片内容进行审核,审核失败需要修改wmNews的reason属性,审核成功则不需要做任何修改
            Boolean imageTest = autoTestImageScan(imagesAddress, wmNews);
            //对审核结果进行判断,如果结果为false则不执行后续的流程
            if (!imageTest) {
                return;
            }
            //审核通过后,调用方法保存文章三剑客
            //通过feign远程用APP端的文章保存业务,需要提前封装请求参数,请求参数从wmNews来,封装为SaveArticleDto
            ResponseResult responseResult = saveAppArticle(wmNews);
            //处理响应结果,判断响应结果中的状态码值
            if (responseResult.getCode() != 200) {
                //状态码不为200,则表示文章三剑客数据新增失败,抛异常
                throw new RuntimeException("APP端文章数据保存失败");
            }
            //状态码为200,则表示数据新增成功,回填文章id即可
            //修改wmNews表中的相关数据
            wmNews.setArticleId((Long) responseResult.getData());
            updateWmNews(wmNews, (short) 9,"审核成功");
        }
    }

    /**
     * 自管理的敏感词审核
     * @param content
     * @param wmNews
     */
    private Boolean TestSensitiveScan(String content, WmNews wmNews) {
        //从数据库中获取所有的敏感词
        QueryWrapper<WmSensitive> queryWrapper =new QueryWrapper<>();
        queryWrapper.select("sensitives");
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(queryWrapper);
        //对查询结果进行处理,取出敏感词
        List<String> sensitives = wmSensitives.stream().map(wmSensitive -> wmSensitive.getSensitives()).collect(Collectors.toList());
        //使用工具类初始化敏感词库
        SensitiveWordUtil.initMap(sensitives);
        //使用工具类,判断文章中是否包含敏感词,包含则map集合中就有值
        Map<String, Integer> map = SensitiveWordUtil.matchWords(content);
        if (map.size() >0){
            //文章包含了敏感词,审核失败,修改wmNews
            updateWmNews(wmNews, (short) 2,"文章包含敏感词审核失败");
            return false;
        }
        return true;
    }
    /**
     * 保存app端文章三剑客数据
     *
     * @param wmNews
     */
    private ResponseResult saveAppArticle(WmNews wmNews) {
        //封装请求数据,WmNews中有部分数据与ApArticle中数据相同,先使用工具类将这部分数据拷贝过去
        SaveArticleDto dto = new SaveArticleDto();
        BeanUtils.copyProperties(wmNews, dto);
        //将其他属性进行封装
        //1封装作者id和名称,根据自媒体用户id进行封装
        if (wmNews.getUserId() != null) {
            dto.setAuthorId(wmNews.getUserId().longValue());
            //根据作者id去wmUser表中查询作者对象
            WmUser wmUser = wmUserService.getById(wmNews.getUserId());
            if (wmUser != null) {
                dto.setAuthorName(wmUser.getName());
            }
        }
        //根据频道id查询频道名称,再将数据封装到dto中
        WmChannel wmChannel = wmChannelService.getById(wmNews.getChannelId());
        if (wmChannel != null) {
            dto.setChannelName(wmChannel.getName());
        }
        //根据type封装文章布局属性layout
        if (wmNews.getType() != null) {
            dto.setLayout(wmNews.getType());
        }
        //封装库文章id属性
        if (wmNews.getArticleId() != null) {
            dto.setId(wmNews.getArticleId());
        }
        //设置创建时间
        dto.setCreatedTime(new Date());
        //请求参数封装完毕,调用feign接口远程访问存储文章三剑客的接口
        ResponseResult responseResult = articleClient.saveArticle3(dto);
        return responseResult;
    }

    /**
     * 自动审核图片方法
     *
     * @param imagesAddress
     * @param wmNews
     */
    private Boolean autoTestImageScan(List<String> imagesAddress, WmNews wmNews) {
        //判断图片地址集合是否为空,不存在直接返回true
        if (imagesAddress == null || imagesAddress.size() == 0) {
            return true;
        }
        //images有值,则需要对其进行去重,因为文本中的图片和封面图片可能有重复的
        List<String> imagePath = imagesAddress.stream().distinct().collect(Collectors.toList());
        //创建一个集合保存下载的图片
        List<byte[]> image = new ArrayList<>();
        //根据地址下载图片
        try {
            for (String path : imagePath) {
                byte[] bytes = fileStorageService.downLoadFile(path);
                //将byte数组转化为ocr审核需要的BufferedImage
                //创建一个流,将图片字节数组读取到流中
                ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
                //读取流中的图片数据
                BufferedImage imageFile = ImageIO.read(inputStream);
                //调用API识别图片中的文字
                String textInImage = tess4jClient.doOCR(imageFile);
                //审核图片的文字中是否包含自管理的敏感词
                Boolean textInImageScanTest = TestSensitiveScan(textInImage, wmNews);
                //判断资管理敏感词审核结果
                if (!textInImageScanTest){
                    //修改审核状态
                    updateWmNews(wmNews, (short) 2,"图片中文字包含违规内容");
                    return false;
                }
                image.add(bytes);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //调用阿里云的图片审核业务
        Map map = null;
        try {
            map = greenImageScan.imageScan(image);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //判断map中是否审核结果,对审核结果进行判断
        if (map != null) {
            //判断审核结果是通过还是失败,通过不予处理,失败要修改数据库中wmNews表中reason字段值
            if (map.get("suggestion").equals("block")) {
                //表示审核失败,修改数据
                updateWmNews(wmNews, (short) 2, "图片包含违规内容");
                return false;
            }
            if (map.get("suggestion").equals("review")) {
                //表示自动审核无法识别,需要人工审核
                updateWmNews(wmNews, (short) 3, "图片存在不确定内容,转人工审核");
                return false;
            }
        }
        return true;
    }

    /**
     * 自动审核文本数据方法
     *
     * @param content
     * @param wmNews
     * @return
     */
    private Boolean autoTestTextScan(String content, WmNews wmNews) {
        //校验文本是否存在,不存在直接返回true
        if (StringUtils.isBlank(content)) {
            return true;
        }
        //文本数据存在则调用阿里云的内容审核方法
        Map map = null;
        try {
            map = greenTextScan.greeTextScan(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //判断map是否存在
        if (map != null) {
            //判断审核结果是通过还是失败,通过不予处理,失败要修改数据库中wmNews表中reason字段值
            if (map.get("suggestion").equals("block")) {
                //表示审核失败,修改数据
                updateWmNews(wmNews, (short) 2, "文本包含违规内容");
                return false;
            }
            if (map.get("suggestion").equals("review")) {
                //表示自动审核无法识别,需要人工审核
                updateWmNews(wmNews, (short) 3, "文本存在不确定内容,转人工审核");
                return false;
            }
        }
        return true;
    }

    /**
     * 审核失败后,修改数据库中WmNews表中的数据
     *
     * @param wmNews
     * @param status
     * @param reason
     */
    private void updateWmNews(WmNews wmNews, short status, String reason) {
        //给图文数据对象设置对应的属性值
        wmNews.setStatus(status);
        wmNews.setReason(reason);
        //调用方法执行修改操作
        wmNewsService.updateById(wmNews);
    }

    /**
     * 获取图文数据中的文本数据和图片数据方法
     *
     * @param wmNews
     * @return
     */
    private Map<String, Object> getTextAndImages(WmNews wmNews) {
        //创建一个stringBuilder容器,封装标题和文本内容
        StringBuilder sb = new StringBuilder();
        //创建一个list集合封装文本和封面图片的地址值
        List<String> imagesAddressList = new ArrayList<>();
        //将标题添加到sb容器中
        sb.append(wmNews.getTitle());
        //判断文章内容是否存在
        if (StringUtils.isNotBlank(wmNews.getContent())) {
            //将文本内容转换类型
            List<Map> maps = JSONArray.parseArray(wmNews.getContent(), Map.class);
            //遍历集合获取文本类容存储到
            for (Map map : maps) {
                //分别获取文本内容和图片地址存入对应的容器中
                if (map.get("type").equals("text")) {
                    sb.append((String) map.get("value"));
                }
                if (map.get("type").equals("image")) {
                    imagesAddressList.add((String) map.get("value"));
                }
            }
        }
        //文本内容封装完毕,图片内容还有封面图片没有进行封装,先判断封面图片是否为空
        if (StringUtils.isNotBlank(wmNews.getImages())) {
            //根据","进行切割
            String[] split = wmNews.getImages().split(",");
            //将数组中的地址数据批量添加到集合中
            imagesAddressList.addAll(Arrays.asList(split));
        }
        //封装数据,创建一个map集合用于封装数据
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("content", sb.toString());
        resultMap.put("images", imagesAddressList);
        return resultMap;
    }
}

